001    /*
002     * Copyright 2005 Stephen J. McConnell.
003     *
004     * Licensed  under the  Apache License,  Version 2.0  (the "License");
005     * you may not use  this file  except in  compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *   http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed  under the  License is distributed on an "AS IS" BASIS,
012     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
013     * implied.
014     *
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package net.dpml.transit;
020    
021    import java.io.File;
022    import java.net.URL;
023    import java.net.URI;
024    import java.rmi.RemoteException;
025    import java.rmi.NoSuchObjectException;
026    import java.rmi.server.UnicastRemoteObject;
027    import java.util.EventObject;
028    import java.util.EventListener;
029    
030    import net.dpml.transit.info.CacheDirective;
031    import net.dpml.transit.info.ProxyDirective;
032    import net.dpml.transit.info.TransitDirective;
033    import net.dpml.transit.model.CacheModel;
034    import net.dpml.transit.model.ProxyModel;
035    import net.dpml.transit.model.TransitModel;
036    import net.dpml.transit.model.DisposalEvent;
037    import net.dpml.transit.model.DisposalListener;
038    import net.dpml.transit.monitor.LoggingAdapter;
039    
040    import net.dpml.util.EventQueue;
041    import net.dpml.util.Logger;
042    
043    /**
044     * The DefaultTransitModel class maintains an active configuration of the 
045     * Transit system.
046     *
047     * @author <a href="http://www.dpml.net">The Digital Product Meta Library</a>
048     * @version 1.0.0
049     */
050    public class DefaultTransitModel extends DefaultModel implements TransitModel
051    {
052        // ------------------------------------------------------------------------
053        // static
054        // ------------------------------------------------------------------------
055        
056       /**
057        * Default configuration url path.
058        */
059        public static final String DEFAULT_PROFILE_PATH = "local:xml:dpml/transit/standard";
060        
061       /**
062        * Default configuration url path.
063        */
064        public static final URI DEFAULT_PROFILE_URI = createStaticURI( DEFAULT_PROFILE_PATH );
065        
066       /**
067        * System property key used to hold an overriding configuration url.
068        */
069        public static final String PROFILE_KEY = "dpml.transit.profile";
070    
071        private static final EventQueue EVENT_QUEUE = new EventQueue( "dpml.transit" );
072    
073       /**
074        * Return a model that is restricted to the secure local environment with 
075        * no proxy setting or external hosts.
076        * @param logger the logging channel to assign to the model
077        * @return the transit model
078        */
079        public static DefaultTransitModel getSecureModel( Logger logger )
080        {
081            try
082            {
083                TransitDirective directive = new TransitDirective( null, new CacheDirective() );
084                if( logger.isTraceEnabled() )
085                {
086                    ClassLoader system = ClassLoader.getSystemClassLoader();
087                    int id = System.identityHashCode( system );
088                    logger.trace( "system classloader id: " + id );
089                }
090                return new DefaultTransitModel( EVENT_QUEUE, logger, directive );
091            }
092            catch( Exception e )
093            {
094                final String error = 
095                  "Unexpected error while constructing static secure model.";
096                throw new RuntimeException( error, e );
097            }
098        }
099        
100       /**
101        * Resolve the transit configuration using the default resource path 
102        * <tt>local:xml:dpml/transit/config</tt>. If the resource does not exist a classic 
103        * default scenario will be returned.
104        *
105        * @return the transit model
106        * @exception Exception if an error occurs during model construction
107        */
108        public static DefaultTransitModel getDefaultModel() throws Exception
109        {
110            return getDefaultModel( "transit" );
111        }
112        
113       /**
114        * Resolve the transit configuration using the default resource path 
115        * <tt>local:xml:dpml/transit/config</tt>. If the resource does not exist a classic 
116        * default scenario will be returned.
117        *
118        * @param category the logging channel category name
119        * @return the transit model
120        * @exception Exception if an error occurs during model construction
121        */
122        public static DefaultTransitModel getDefaultModel( String category ) throws Exception
123        {
124            LoggingAdapter adapter = new LoggingAdapter( category );
125            return getDefaultModel( adapter );
126        }
127        
128       /**
129        * Resolve the transit configuration using the default resource path 
130        * <tt>local:xml:dpml/transit/config</tt>. If the resource does not exist a classic 
131        * default scenario will be returned.
132        *
133        * @param logger the logging channel
134        * @return the transit model
135        * @exception Exception if an error occurs during model construction
136        */
137        public static DefaultTransitModel getDefaultModel( Logger logger ) throws Exception
138        {
139            String path = System.getProperty( PROFILE_KEY );
140            if( logger.isTraceEnabled() )
141            {
142                ClassLoader system = ClassLoader.getSystemClassLoader();
143                int id = System.identityHashCode( system );
144                logger.trace( "system classloader id: " + id );
145            }
146            if( null != path )
147            {
148                URL url = Artifact.createArtifact( path ).toURL();
149                TransitBuilder builder = new TransitBuilder( logger );
150                TransitDirective directive =  builder.load( url );
151                return new DefaultTransitModel( EVENT_QUEUE, logger, directive );
152            }
153            else
154            {
155                File prefs = Transit.DPML_PREFS;
156                File config = new File( prefs, "dpml/transit/xmls/standard.xml" );
157                if( config.exists() )
158                {
159                    URL url = config.toURL();
160                    TransitBuilder builder = new TransitBuilder( logger );
161                    TransitDirective directive =  builder.load( url );
162                    return new DefaultTransitModel( EVENT_QUEUE, logger, directive );
163                }
164                else
165                {
166                    return getClassicModel( logger );
167                }
168            }
169        }
170        
171        // ------------------------------------------------------------------------
172        // state
173        // ------------------------------------------------------------------------
174        
175        private final DefaultProxyModel m_proxy;
176        private final DefaultCacheModel m_cache;
177    
178        // ------------------------------------------------------------------------
179        // constructor
180        // ------------------------------------------------------------------------
181    
182       /**
183        * Creation of a new TransitModel using a supplied configuration
184        * and logging channel.  The implementation will construct a proxy
185        * model, layout registry model, cache model, and repository codebase 
186        * model using the supplied configuration.
187        *
188        * @param logger the assigned loging channel
189        * @param directive the transit configuration
190        * @exception NullPointerException if the logger or directive arguments are null
191        * @exception RemoteException if a remote exception occurs
192        */
193        public DefaultTransitModel( Logger logger, TransitDirective directive ) 
194          throws RemoteException, NullPointerException
195        {
196            this( EVENT_QUEUE, logger, directive );
197        }
198        
199       /**
200        * Creation of a new TransitModel using a supplied configuration
201        * and logging channel.  The implementation will construct a proxy
202        * model, layout registry model, cache model, and repository codebase 
203        * model using the supplied configuration.
204        *
205        * @param queue the event queue 
206        * @param logger the assigned logging channel
207        * @param directive the transit configuration
208        * @exception NullPointerException if the logger or directive arguments are null
209        * @exception RemoteException if a remote exception occurs
210        */
211        public DefaultTransitModel( EventQueue queue, Logger logger, TransitDirective directive ) 
212          throws RemoteException, NullPointerException
213        {
214            super( queue, logger );
215    
216            if( null == directive )
217            {
218                throw new NullPointerException( "directive" );
219            }
220    
221            m_proxy = createProxyModel( directive );
222            m_cache = createCacheModel( directive );
223        }
224    
225        // ------------------------------------------------------------------------
226        // TransitModel
227        // ------------------------------------------------------------------------
228    
229       /**
230        * Return the proxy configuration model.
231        * @return the proxy model (null if no proxy config defined).
232        */
233        public ProxyModel getProxyModel()
234        {
235            return m_proxy;
236        }
237    
238       /**
239        * Return the cache model.
240        * @return the cache model 
241        */
242        public CacheModel getCacheModel()
243        {
244            return m_cache;
245        }
246    
247       /**
248        * Add a disposal listener to the model.
249        * @param listener the listener to add
250        */
251        public void addDisposalListener( DisposalListener listener )
252        {
253            super.addListener( listener );
254        }
255    
256       /**
257        * Remove a disposal listener from the model.
258        * @param listener the listener to remove
259        */
260        public void removeDisposalListener( DisposalListener listener )
261        {
262            super.removeListener( listener );
263        }
264    
265       /**
266        * Internal event handler.
267        * @param eventObject the event to handle
268        */
269        public void processEvent( EventObject eventObject )
270        {
271            if( eventObject instanceof DisposalEvent )
272            {
273                DisposalEvent event = (DisposalEvent) eventObject;
274                processDisposalEvent( event );
275            }
276        }
277        
278        private void processDisposalEvent( DisposalEvent event )
279        {
280            EventListener[] listeners = super.getEventListeners();
281            for( int i=0; i < listeners.length; i++ )
282            {
283                EventListener listener = listeners[i];
284                if( listener instanceof DisposalListener )
285                {
286                    DisposalListener pl = (DisposalListener) listener;
287                    try
288                    {
289                        pl.notifyDisposal( event );
290                    }
291                    catch( Throwable e )
292                    {
293                        final String error =
294                          "Disposal notification error.";
295                        getLogger().error( error, e );
296                    }
297                }
298            }
299        }
300        
301        // ------------------------------------------------------------------------
302        // impl
303        // ------------------------------------------------------------------------
304    
305        Logger getLoggingChannel()
306        {
307            return getLogger();
308        }
309    
310       /**
311        * Trigger disposal of the transit model.
312        */
313        public synchronized void dispose()
314        {
315            DisposalEvent event = new DisposalEvent( this );
316            enqueueEvent( event, true );
317            disposeCacheModel();
318            disposeProxyModel();
319            super.dispose();
320            EVENT_QUEUE.terminateDispatchThread();
321            Thread thread = new Terminator( this );
322            thread.start();
323        }
324        
325       /**
326        * Internal model terminator.
327        */
328        private class Terminator extends Thread
329        {
330            private final DefaultTransitModel m_model;
331            Terminator( DefaultTransitModel model )
332            {
333                m_model = model;
334            }
335            
336           /**
337            * Initiate model retraction from the RMI.
338            */
339            public void run()
340            {
341                try
342                {
343                    UnicastRemoteObject.unexportObject( m_model, true );
344                }
345                catch( NoSuchObjectException e )
346                {
347                    // ignore
348                }
349                catch( RemoteException e )
350                {
351                    e.printStackTrace();
352                }
353            }
354        }
355        
356        private synchronized void disposeProxyModel()
357        {
358            if( null == m_proxy )
359            {
360                return;
361            }
362            else
363            {
364                m_proxy.dispose();
365                try
366                {
367                    UnicastRemoteObject.unexportObject( m_proxy, true );
368                }
369                catch( NoSuchObjectException e )
370                {
371                    // ignore
372                }
373                catch( RemoteException e )
374                {
375                    getLogger().warn( "Remote error during proxy reference removal.", e );
376                } 
377            }
378        }
379        
380        private synchronized void disposeCacheModel()
381        {
382            m_cache.dispose();
383            try
384            {
385                UnicastRemoteObject.unexportObject( m_cache, true );
386            }
387            catch( NoSuchObjectException e )
388            {
389                // ignore
390            }
391            catch( RemoteException e )
392            {
393                getLogger().warn( "Remote error during cache reference removal.", e );
394            }
395        }
396        
397        private DefaultProxyModel createProxyModel( final TransitDirective directive )
398        {
399            try
400            {
401                ProxyDirective config = directive.getProxyDirective();
402                if( null == config )
403                {
404                    return null;
405                }
406                else
407                {
408                    Logger logger = getLogger().getChildLogger( "proxy" );
409                    return new DefaultProxyModel( EVENT_QUEUE, logger, config );
410                }
411            }
412            catch( Throwable e )
413            {
414                final String error = 
415                  "An error occured during construction of the proxy model.";
416                throw new TransitError( error, e );
417            }
418        }
419    
420        private DefaultCacheModel createCacheModel( final TransitDirective directive )
421        {
422            try
423            {
424                Logger logger = getLogger().getChildLogger( "cache" );
425                CacheDirective config = directive.getCacheDirective();
426                return new DefaultCacheModel( EVENT_QUEUE, logger, config );
427            }
428            catch( Throwable e )
429            {
430                final String error = 
431                  "An error occured during construction of the cache model.";
432                throw new TransitError( error, e );
433            }
434        }
435    
436        static DefaultTransitModel getBootstrapModel() throws Exception
437        {
438            Logger logger = new LoggingAdapter( "transit" );
439            return getSecureModel( logger );
440        }
441        
442        static DefaultTransitModel getClassicModel( Logger logger ) throws Exception
443        {
444            TransitDirective directive = TransitDirective.CLASSIC_PROFILE;
445            return new DefaultTransitModel( EVENT_QUEUE, logger, directive );
446        }
447        
448        private static URI createStaticURI( String path )
449        {
450            try
451            {
452                return Artifact.createArtifact( path ).toURI();
453            }
454            catch( Exception e )
455            {
456                return null;
457            }
458        }
459        
460    }
461